<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>大文件上传</title>
  <style>
    .upload {
      width: 200px;
      height: 200px;
      border: 1px solid #333;
      position: relative;
      text-align: center;
      line-height:200px;
      font-size: 100px;
    }
    .upload input {
      position: absolute;
      top: 0;
      left: 0;
      opacity: 0;
      width: 100%;
      height: 100%;
    }
  </style>
</head>
<body>
  <div class="upload">
    +
    <input type="file" id="file" />
  </div>
  <ul class="uploadProgressList">

  </ul>
  <script>
    /**
     * 请求方法
     */
    function request({
      url,
      method = 'POST',
      data,
      headers = {},
      onProgress = ()=>{},
      requestList
    }){
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        Object.keys(headers).forEach(keys => {
          xhr.setRequestHeader(keys, headers[keys]);
        })
        xhr.upload.onprogress = function({loaded,total}){
          onProgress((loaded / total * 100).toFixed(2))
        }
        xhr.onload = (e) => {
          resolve({
            data:e.target.response
          })
        }
        xhr.send(data);
      })
    }
  </script>
  <script>
    const fileBtn = document.getElementById("file");
    const SIZE = 10 * 1024 * 1024; // 切片大小
    const uploadProgressList = document.querySelector(".uploadProgressList")
    function createFileChunkList(file,size = SIZE) {
      const filesChunkList = [];
      let curSize = 0;
      while (curSize < file.size) {
        filesChunkList.push({
          file:file.slice(curSize,curSize+size)
        });
        curSize += size;
      }
      return filesChunkList;
    }
    function createLi (filename,index){
      const li = document.createElement('li');
      const span = document.createElement('span');
      const input = document.createElement('input');
      const b = document.createElement('b');
      span.innerHTML = filename+'_'+index;
      input.value = 0;
      input.min = 1;
      input.disabled = true;
      input.max = 100;
      input.type = 'range';
      b.innerHTML = `0%`;
      li.append(span);
      li.append(input);
      li.append(b);
      return li;
    }
    function formatFileChunkList(filename,filesChunkList){
      const lis = document.createDocumentFragment();
      const res = filesChunkList.map(({file},index)=>{
        lis.append(createLi(filename,index));
        return  {
          file,
          chunkName:filename+'_'+index,
          li
        }
      })
      uploadProgressList.append(lis);
      return res;
    }
    async function uploadFilesChunk(filename,filesChunkList){
      const list = filesChunkList.map(({file,chunkName,li}) =>{
        const formDatas = new FormData();
        formDatas.append('file',file);
        formDatas.append('chunkname',chunkName);
        formDatas.append('filename',filename);
        return request({
          url:'/uploadChunk',
          data:formDatas,
          onProgress (data) {
            const input = li.querySelector('input');
            const b = li.querySelector('b');
            input.value = data;
            b.innerHTML = data + '%';
          }
        })
      })
      await Promise.all(list);
    }
    async function mergeRequest(filename,size){
      await request({
        url:'/mergeChunk',
        headers: {
          "content-type": "application/json"
        },
        data:JSON.stringify({
          filename,
          SIZE
        })
      })
    }
    fileBtn.addEventListener('change',async function(){
      const _file = this.files[0];
      const _data = [];
      if(!_file){
        return;
      }
      const filesChunkList = formatFileChunkList(_file.name,createFileChunkList(_file));
      await uploadFilesChunk(_file.name,filesChunkList);
      await mergeRequest(_file.name);
      console.log('合并切片成功')
    },false)


  </script>
</body>
</html>